#![allow(non_snake_case)]
use chrono::{Datelike, Days, Local, Months, NaiveDate};
use icondata as i;
use leptos::prelude::*;
use leptos_icons::*;
use std::ops::Deref;
use thaw_utils::Model;

#[component]
pub fn Calendar(
    #[prop(optional, into)] value: Model<Option<NaiveDate>>,
) -> impl IntoView {
    let show_date = RwSignal::new(value.get_untracked().unwrap_or(now_date()));
    Effect::new(move |_| {
        if let Some(selected_date) = value.get() {
            let show_date_data = show_date.get_untracked();
            if selected_date.year() != show_date_data.year()
                || selected_date.month() != show_date_data.month()
            {
                show_date.set(selected_date);
            }
        }
    });

    let dates = Memo::new(move |_| {
        let show_date = show_date.get();
        let show_date_month = show_date.month();
        let mut dates = vec![];

        let mut current_date = show_date;
        let mut current_weekday_number = None::<u32>;
        loop {
            let date = current_date - Days::new(1);
            if date.month() != show_date_month {
                if current_weekday_number.is_none() {
                    current_weekday_number = Some(current_date.weekday().num_days_from_sunday());
                }
                let weekday_number = current_weekday_number.unwrap();
                if weekday_number == 0 {
                    break;
                }
                current_weekday_number = Some(weekday_number - 1);

                dates.push(CalendarItemDate::Previous(date));
            } else {
                dates.push(CalendarItemDate::Current(date));
            }
            current_date = date;
        }
        dates.reverse();
        dates.push(CalendarItemDate::Current(show_date));
        current_date = show_date;
        current_weekday_number = None;
        loop {
            let date = current_date + Days::new(1);
            if date.month() != show_date_month {
                if current_weekday_number.is_none() {
                    current_weekday_number = Some(current_date.weekday().num_days_from_sunday());
                }
                let weekday_number = current_weekday_number.unwrap();
                if weekday_number == 6 {
                    break;
                }
                current_weekday_number = Some(weekday_number + 1);
                dates.push(CalendarItemDate::Next(date));
            } else {
                dates.push(CalendarItemDate::Current(date));
            }
            current_date = date;
        }
        dates
    });

    let previous_month = move |_| {
        show_date.update(|date| {
            *date = *date - Months::new(1);
        });
    };
    let today = move |_| {
        show_date.set(Local::now().date_naive());
    };
    let next_month = move |_| {
        show_date.update(|date| {
            *date = *date + Months::new(1);
        });
    };

    view! {
        <div
            class="w-full"
        >
            <div class="navbar bg-base-100">
                <div class="navbar-start">
                    {move || {
                        show_date
                            .with(|date| {
                                format!(
                                    "{}年{}月",
                                    date.year(),
                                    date.month(),
                                )
                            })
                    }}
                </div>
                <div class="navbar-end">
                    <div class="join">
                        <button class="btn btn-outline join-item" on:click=previous_month>
                            <Icon icon=i::AiLeftOutlined />
                        </button>
                        <button class="btn btn-outline join-item" on:click=today>
                            今天
                        </button>
                        <button class="btn btn-outline join-item" on:click=next_month>
                            <Icon icon=i::AiRightOutlined />
                        </button>
                    </div>
                </div>
            </div>
            <div class="grid grid-cols-7 gap-1">
                {move || {
                    dates
                        .get()
                        .into_iter()
                        .enumerate()
                        .map(|(index, date)| {
                            view! {
                                <CalendarItem value index=index date=date/>
                            }
                        })
                        .collect_view()
                }}
            </div>
        </div>
    }
}

#[component]
fn CalendarItem(
    value: Model<Option<NaiveDate>>,
    index: usize,
    date: CalendarItemDate,
) -> impl IntoView {
    let is_selected = Memo::new({
        let date = date.clone();
        move |_| value.with(|value_date| value_date.as_ref() == Some(date.deref()))
    });
    let weekday_str = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
    let on_click = {
        let date = date.clone();
        move |_| {
            value.set(Some(*date.deref()));
        }
    };
    view! {
        <div
            class="border border-solid bg-base-100 h-24 flex flex-row"
            class=("text-blue-800", date.is_today())
            class=("text-neutral-400", date.is_other_month())
            class=("border-blue-800", move || is_selected.get())
            class=("border-slate-300", move || !is_selected.get())
            on:click=on_click
        >
           <div class="flex-1 text-left ml-2">
                 {date.day()}
            </div>
            {if index < 7 {
                view! {
                    <div class="flex-none text-right mr-2">{weekday_str[index]}</div>
                }
                    .into()
            } else {
                None
            }}
        </div>
    }
}

#[derive(Clone, PartialEq)]
pub(crate) enum CalendarItemDate {
    Previous(NaiveDate),
    Current(NaiveDate),
    Next(NaiveDate),
}

impl CalendarItemDate {
    pub fn is_other_month(&self) -> bool {
        match self {
            CalendarItemDate::Previous(_) | CalendarItemDate::Next(_) => true,
            CalendarItemDate::Current(_) => false,
        }
    }

    pub fn is_today(&self) -> bool {
        let date = self.deref();
        let now_date = now_date();
        &now_date == date
    }
}

impl Deref for CalendarItemDate {
    type Target = NaiveDate;

    fn deref(&self) -> &Self::Target {
        match self {
            CalendarItemDate::Previous(date)
            | CalendarItemDate::Current(date)
            | CalendarItemDate::Next(date) => date,
        }
    }
}

pub(crate) fn now_date() -> NaiveDate {
    Local::now().date_naive()
}
